package com.leyou.search.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.entity.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecClient;
import com.leyou.search.entity.Goods;
import com.leyou.search.entity.SearchRequest;
import com.leyou.search.entity.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.LySearchService;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.aspectj.weaver.ast.Var;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author linzx
 * @date 2020/3/26
 */
@Service
public class LySearchServiceImpl implements LySearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate esTemplate;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private SpecClient specClient;
    @Autowired
    private ElasticsearchTemplate template;

    @Override
    public void createIndexsAndMapping(Class clazz) {
        if (esTemplate.indexExists(clazz)) {
            deleteIndex(clazz);
        }
        if (!esTemplate.createIndex(clazz) || !esTemplate.putMapping(clazz)) {
            throw new RuntimeException("创建索引失败");
        }

    }

    @Override
    public void deleteIndex(Class clazz) {
        if (!esTemplate.deleteIndex(clazz)) {
            throw new RuntimeException("删除ES索引失败");
        }
    }

    @Override
    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();
        //品牌
        Brand brand = brandClient.findBrandById(spu.getBrandId());
        //分类名称
        List<String> cnames = categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //sku集合
        List<Sku> skuList = goodsClient.findSkuBySpuId(spu.getId());
        List<Long> prices = new ArrayList<>();
        List<Map<String,Object>> skuMapList = new ArrayList<>();
        skuList.forEach(sku -> {
            //每个sku价格集合
            prices.add(sku.getPrice());
            Map<String,Object> skuMap = new HashMap<>();
            skuMap.put("id",sku.getId());
            skuMap.put("title",sku.getTitle());
            skuMap.put("price",sku.getPrice());
            skuMap.put("image",
                    StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(),",")[0] : ""
                    );
            skuMapList.add(skuMap);
        });
        // 查询出所有的搜索规格参数
        List<SpecParam> specParamList = specClient.findSpecByParams(spu.getCid3(), null, true);
        // 查询spuDetail。获取规格参数值
        SpuDetail detail = goodsClient.findSpuDetailById(spu.getId());
        // 获取通用的规格参数
        Map<Long, Object> genericSpecMap = JsonUtils.nativeRead(detail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {
        });
        // 获取特殊的规格参数
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(detail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
        });
        // 定义map接收{规格参数名，规格参数值}
        Map<String,Object> paramMap = new HashMap<>();
        specParamList.forEach(param -> {
            // 判断是否通用规格参数
            if (param.getGeneric()) {
                // 获取通用规格参数值
                String value = genericSpecMap.get(param.getId()).toString();
                // 判断是否是数值类型
                if (param.getNumeric()) {
                    // 如果是数值的话，判断该数值落在那个区间
                    value = chooseSegment(value, param);
                }
                // 把参数名和值放入结果集中
                paramMap.put(param.getName(), value);
            }else {
                paramMap.put(param.getName(), specialSpecMap.get(param.getId()));
            }
        });
        goods.set(
                spu.getId(),spu.getTitle() + brand.getName() + StringUtils.join(cnames," "),
                spu.getSubTitle(),brand.getId(),spu.getCid1(),spu.getCid2(),spu.getCid3(),
                spu.getCreateTime(),prices,JsonUtils.serialize(skuList),paramMap
        );
        return goods;
    }

    @Override
    public SearchResult search(SearchRequest request) {
        Integer page = request.getPage() -1;
        Integer size = request.getSize();
        //分页查询构建构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //_source
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));
        //page
        queryBuilder.withPageable(PageRequest.of(page,size));
        //query match "all":key
        QueryBuilder baseQuery = builderBasicQuery(request);

        queryBuilder.withQuery(baseQuery);
        //聚合分类和品牌
        queryBuilder.addAggregation(AggregationBuilders.terms("category_agg").field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brand_agg").field("brandId"));

//        Page<Goods> result = goodsRepository.search(queryBuilder.build());

        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);


        int totalPages = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        long totalElements = result.getTotalElements();

        Aggregations aggs = result.getAggregations();

        List<Category> categories = parseCategoryAgg(aggs.get("category_agg"));
        List<Map<String, Object>> specs = new ArrayList<>();
        if (categories != null && categories.size() == 1) {
            specs = buildSpecsAgg(categories.get(0).getId(),baseQuery);
        }
        List<Brand> brands = parseBrandAgg(aggs.get("brand_agg"));

        return new SearchResult(totalElements,totalPages,goodsList,categories,brands , specs);
    }

    @Override
    public void deleteIndexById(Long spuId) {
        goodsRepository.deleteById(spuId);
    }

    @Override
    public void creatOrUpdateIndex(Long spuId) throws Exception{
        Spu spu = goodsClient.querySpuById(spuId);
        Goods goods = this.buildGoods(spu);
        goodsRepository.save(goods);
    }

    private List<Map<String, Object>> buildSpecsAgg(Long id, QueryBuilder baseQuery) {
        List<Map<String, Object>> specs = new ArrayList<>();
        List<SpecParam> params = specClient.findSpecByParams(id, null, true);

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(baseQuery);
        for (SpecParam param : params) {
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword")) ;
        }

        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggs = result.getAggregations();
        for (SpecParam param : params) {
            String name = param.getName();
            StringTerms terms = aggs.get(name);
            List<String> options = terms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            Map<String,Object> map = new HashMap<>();
            map.put("k",name);
            map.put("options",options);
            specs.add(map);
        }
        return specs;
    }

    private QueryBuilder builderBasicQuery(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()));
        Map<String, String> map = request.getFilter();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            if (!"brandId".equals(key) && !"cid3".equals(key)) {
                key = "specs."+key+".keyword";
            }
            String value = entry.getValue();
            queryBuilder.filter(QueryBuilders.termQuery(key,value));
        }
        return queryBuilder;
    }

    private List<Brand> parseBrandAgg(LongTerms terms) {
        try {
            List<Long> brandIds = terms.getBuckets().stream().map(bucket ->
                bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Brand> brandList = brandClient.findBrandByIds(brandIds);
            return brandList;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    private List<Category> parseCategoryAgg(LongTerms terms) {
        try {
            List<Long> Ids = terms.getBuckets().stream().map(bucket ->
                    bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
            return categoryClient.queryCategoryByIds(Ids);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }
}
